package com.ivy.parser.logicflow;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.ivy.builder.graph.*;
import com.ivy.parser.bus.ELBusCatch;
import com.ivy.parser.utils.CommonUtil;
import com.ivy.parser.utils.ELJsonUtil;
import com.ivy.parser.utils.FlowConvertELUtil;
import com.yomahub.liteflow.builder.el.ELWrapper;
import com.yomahub.liteflow.builder.el.WhenELWrapper;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import lombok.Data;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Data
public class LogicFlowGraphEL {
    private Map<Node, List<Node>> list;//正序
    private Map<Node, List<Node>> reverseList;//倒序
    private List<Edge> edgeList;
    private Map<Long, IvyCmp> nodeInfoMap;
    private List<Node> groupParallelList;
    private List<Node> groupCatchList;
    private List<Node> preList;
    private List<Node> finallyList;
    private List<Node> fallbackList;

    private Node startNode;
    private List<Node> startNodeList;
    private Node endNode;
    private List<Node> endNodeList;
    private List<Node> forkNodeList;//分叉节点
    private List<Node> joinNodeList;//聚合节点

    public LogicFlowGraphEL() {
        this.list = new LinkedHashMap<>();
        this.reverseList = new LinkedHashMap<>();
    }

    public static LogicFlowGraphEL getGraphEL(LogicFlowData logicFlowData){
        return getGraphEL(logicFlowData,logicFlowData.getIvyCmpMap());
    }

    public static LogicFlowGraphEL getGraphEL(LogicFlowData logicFlowData, Map<Long,IvyCmp> nodeInfoMap){
        LogicFlowGraphEL graph = new LogicFlowGraphEL();
        graph.setNodeInfoMap(nodeInfoMap);
        graph.setGroupParallelList(logicFlowData.getGroupParallelList());
        graph.setGroupCatchList(logicFlowData.getGroupCatchList());
        graph.setPreList(handlerPreFinally(logicFlowData.getPreList(),nodeInfoMap));
        graph.setFinallyList(handlerPreFinally(logicFlowData.getFinallyList(),nodeInfoMap));
        graph.setFallbackList(handlerFallback(logicFlowData.getFallbackList(),nodeInfoMap));
        List<Node> nodes = logicFlowData.getNodes();
        List<Node> startNodeList = new ArrayList<>();
        Map<String, Node> nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, m -> m));
        for (Node node : nodes) {
            graph.addNode(node);
        }
        List<Edge> edges = logicFlowData.getEdges();
        graph.setEdgeList(edges);
        Set<String> targetNodes = new HashSet<>();
        for (Edge edge : edges) {
            graph.addEdge(nodeMap.get(edge.getSourceNodeId()), nodeMap.get(edge.getTargetNodeId()));
            targetNodes.add(edge.getTargetNodeId());
        }
        if(!edges.isEmpty()){
            for (Edge edge : edges) {
                String sourceNodeId = edge.getSourceNodeId();
                if (!targetNodes.contains(sourceNodeId)) {
                    Node startNode = nodeMap.get(sourceNodeId);
                    graph.setStartNode(startNode);
                    if(!startNodeList.contains(startNode)){
                        startNodeList.add(startNode);
                    }
                }
            }
        }else{
            graph.setStartNode(nodes.get(0));
        }

        List<Node> endNodeList = graph.getList().entrySet().stream()
                .filter(entry -> entry.getValue().isEmpty())
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        graph.setEndNode(endNodeList.stream().findFirst().orElse(null));
        graph.setStartNodeList(startNodeList);
        graph.setEndNodeList(endNodeList);
        handlerFork(graph);
        handlerJoin(graph);
        return graph;
    }

    private static void handlerFork(LogicFlowGraphEL graph) {
        graph.setForkNodeList(graph.getList().entrySet().stream().map(m -> {
            if (m.getValue().size() > 1) {
                return m.getKey();
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList()));
    }
    private static void handlerJoin(LogicFlowGraphEL graph) {
        graph.setJoinNodeList(graph.getReverseList().entrySet().stream().map(m -> {
            if (m.getValue().size() > 1) {
                return m.getKey();
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList()));
    }

    private static List<Node> handlerFallback(List<Node> nodes,Map<Long,IvyCmp> nodeInfoMap) {
        if(nodes != null && !nodes.isEmpty()){
            for (Node node : nodes){
                NodeProperties prop = node.getProperties();
                if(prop.getFallbackCommonId() != null){
                    prop.setFallbackType(NodeTypeEnum.COMMON.getCode());
                }else if(prop.getFallbackSwitchId() != null){
                    prop.setFallbackType(NodeTypeEnum.SWITCH.getCode());
                }else if(prop.getFallbackIfId() != null){
                    prop.setFallbackType(NodeTypeEnum.IF.getCode());
                }else if(prop.getFallbackForId() != null){
                    prop.setFallbackType(NodeTypeEnum.FOR.getCode());
                }else if(prop.getFallbackWhileId() != null){
                    prop.setFallbackType(NodeTypeEnum.WHILE.getCode());
                }else if(prop.getFallbackBreakId() != null){
                    prop.setFallbackType(NodeTypeEnum.BREAK.getCode());
                }else if(prop.getFallbackIteratorId() != null){
                    prop.setFallbackType(NodeTypeEnum.ITERATOR.getCode());
                }
            }
        }
        return nodes;
    }

    private static List<Node> handlerPreFinally(List<Node> nodes,Map<Long,IvyCmp> nodeInfoMap) {
        if(nodes == null || nodes.isEmpty()){
            return null;
        }
        List<Node> nodeList = new ArrayList<>();
        for (Node node : nodes){
            NodeProperties properties = node.getProperties();
            if(properties != null){
                String[] ids = properties.getIds();
                if(ids != null){
                    for (String id : ids){
                        NodeProperties nodeInfoWrapper = new NodeProperties();
                        BeanUtil.copyProperties(nodeInfoMap.get(Long.parseLong(id)), nodeInfoWrapper);
                        Node n = new Node();
                        n.setId(node.getId());
                        n.setType(node.getType());
                        n.setText(n.getText());
                        n.setProperties(nodeInfoWrapper);
                        nodeList.add(n);
                    }
                }else if(properties.getId() != null){
                    NodeProperties nodeInfoWrapper = new NodeProperties();
                    BeanUtil.copyProperties(nodeInfoMap.get(properties.getId()), nodeInfoWrapper);
                    Node n = new Node();
                    n.setId(node.getId());
                    n.setType(node.getType());
                    n.setText(n.getText());
                    n.setProperties(nodeInfoWrapper);
                    nodeList.add(n);
                }
            }
        }
        return nodeList;
    }

    public void addNode(Node node) {
        list.put(node, new ArrayList<>());
        reverseList.put(node, new ArrayList<>());
    }

    public void addEdge(Node sourceNode, Node targetNode) {
        list.get(sourceNode).add(targetNode);
        reverseList.get(targetNode).add(sourceNode);
    }

    // 获取起始节点
    // 获取结束节点
    // 获取下一个节点
    public List<Node> getNextNode(Node startNode) {
        return list.get(startNode);
    }

    public List<Node> getPrevNode(Node startNode) {
        return reverseList.get(startNode);
    }
    // 获取上一个节点

    // 判断是否是单起点
    public boolean isSingeStart(){
        return startNodeList.size() <= 1;
    }

    // 判断是否是多起点
    public boolean isMultipleStart(){
        return !isSingeStart();
    }

    // 判断是否是分叉节点
    public boolean isFork(Node node){
        return forkNodeList.contains(node);
    }

    // 判断是否是聚合节点
    public boolean isJoin(Node node){
        return joinNodeList.contains(node);
    }

    // 判断是否是结束节点
    public boolean isLastNode(Node node){
        return list.get(node).size() == 0;
    }

    public Edge getEdge(Node startNode, Node endNode){
        return edgeList.stream().filter(m -> m.getSourceNodeId().equals(startNode.getId()) && m.getTargetNodeId().equals(endNode.getId())).findFirst().orElse(null);
    }

    public EdgeProperties getEdgeProp(Node startNode, Node endNode){
        Edge edge = getEdge(startNode, endNode);
        if(edge != null){
            return edge.getProperties();
        }
        return null;
    }

    public boolean isLinkType(Node startNode, Node endNode, int type){
        EdgeProperties edgeProp = getEdgeProp(startNode, endNode);
        return edgeProp != null && edgeProp.getLinkType() == type;
    }

    // 是否普通路径
    public boolean isCommonLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 0);
    }

    // 是否Switch to路径
    public boolean isSwitchToLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 1);
    }

    // 是否Switch default路径
    public boolean isSwitchDefaultLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 2);
    }

    public boolean isIfTrueLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 3);
    }

    public boolean isIfFalseLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 4);
    }


    public boolean isForLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 5);
    }


    public boolean isWhileLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 6);
    }


    public boolean isIteratorLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 7);
    }

    public boolean isBreakLine(Node startNode, Node endNode) {
        return isLinkType(startNode, endNode, 8);
    }

    public boolean isMultipleJoin(Node startNode,Node joinNode) {
        Map<Node,List<List<Node>>> joinNodeMap = new HashMap<>();
        for (Node node : joinNodeList){
            List<List<Node>> allPaths = getAllPaths(startNode, node, false);
            // 获取在所有路径中相同节点只有2个的聚合节点
            List<Node> commonNodes = getAllPathSameNode(allPaths);
            if(commonNodes.size() == 2){
                joinNodeMap.put(node, allPaths);
            }
        }
        List<List<Node>> allPaths = joinNodeMap.remove(joinNode);
        Map<Node, Long> nodeCountMap = allPaths.stream()
                .flatMap(List::stream)
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        boolean flag = false;
        for (Map.Entry<Node,List<List<Node>>> entry : joinNodeMap.entrySet()){
            Long count = nodeCountMap.get(entry.getKey());
            if(count.intValue() != allPaths.size()){
                flag = true;
            }
        }
        return flag;
    }

    //根据单起点寻找聚合节点
    public Node getJoinNode(Node startNode) {
        for (Node node : joinNodeList){
            List<Node> nextNodeList = getNextNode(startNode);
            List<List<Node>> allPaths = getAllPaths(startNode, node, false);
            if(allPaths.size() >= nextNodeList.size()){
                // 获取在所有路径中相同节点只有2个的聚合节点
                List<Node> commonNodes = getAllPathSameNode(allPaths);
                if(commonNodes.size() == 2){
                    return node;
                }
            }
        }
        return null;
    }

    //根据多起点寻找聚合节点
    public Node getJoinNode(List<Node> startNodeList) {
        for (Node node : joinNodeList){
            List<List<Node>> allPaths = new ArrayList<>();
            for (Node startNode : startNodeList){
                List<List<Node>> paths = getAllPaths(startNode, node, false);
                allPaths.addAll(paths);
            }
            if(allPaths.size() >= startNodeList.size()) {
                List<Node> commonNodes = getAllPathSameNode(allPaths);
                if (commonNodes.size() == 1) {
                    return node;
                }
            }
        }
        return null;
    }

    // 获取在所有路径中相同节点
    public List<Node> getAllPathSameNode(List<List<Node>> allPaths){
        return allPaths.stream()
                .flatMap(List::stream) // 扁平化为节点流
                .collect(Collectors.groupingBy(Node::getId)) // 按节点值分组
                .entrySet().stream()
                .filter(entry -> entry.getValue().size() == allPaths.size()) // 筛选在所有列表中都存在的节点
                .map(entry -> entry.getValue().get(0))
                .collect(Collectors.toList());
    }

    //判断是否存在相交节点
    public boolean isXNode(List<Node> startNodeList, Node endNode) {
        for (Node startNode : startNodeList){
            List<List<Node>> paths = getAllPaths(startNode, endNode, true);
            boolean hasCommonNodes = paths.stream()
                    .flatMap(List::stream)
                    .anyMatch(joinNodeList::contains);
            System.out.println();
            if(hasCommonNodes){
                return true;
            }
        }
        return false;
    }

    //起点集合分组
    public Set<List<Node>> startNodeGroupList(List<Node> startNodeList, Node endNode) {
        List<List<Node>> lists = new ArrayList<>();
        Map<Node,List<Node>> nodeListMap = new HashMap<>();
        Set<Node> sameNodeSet = new HashSet<>();
        for (Node startNode : startNodeList){
            List<List<Node>> allPaths = getAllPaths(startNode, endNode, true);
            if(allPaths.size() > 1){
                List<Node> allPathSameNode = getAllPathSameNode(allPaths);
                nodeListMap.put(startNode, allPathSameNode);
                sameNodeSet.addAll(allPathSameNode);
            }else{
                lists.add(CollUtil.toList(startNode));
            }
        }
        sameNodeSet.forEach(m->{
            List<Node> nodeList = nodeListMap.entrySet().stream().filter(n -> n.getValue().contains(m)).map(Map.Entry::getKey).collect(Collectors.toList());
            lists.add(nodeList);
        });
        // 去除lists重复元素
        return new HashSet<>(lists);
    }

    //startNode节点下有多个子节点 并且 存在相交的清空
    public Set<List<Node>> isMultipleStartAndXNode(Node startNode, Node endNode) {
        List<Node> nextNode = getNextNode(startNode);
        if(nextNode.size() > 1){
            return startNodeGroupList(nextNode, endNode);
        }
        return null;
    }

    // 获取从给定节点开始的所有路径
    public List<List<Node>> getAllPaths(Node startNode, boolean excludeStartAndEnd) {
        List<List<Node>> allPaths = getAllPaths(startNode);
        return allPaths.stream().peek(m->m.remove(startNode)).collect(Collectors.toList());
    }

    public List<List<Node>> getAllPaths(Node startNode) {
        List<List<Node>> paths = new ArrayList<>();
        List<Node> currentPath = new ArrayList<>();
        Set<Node> visited = new HashSet<>();
        dfsGetAllPaths(startNode, currentPath, paths, visited);
        return paths;
    }

    // 获取从给定节点开始到结束节点之间的所有路径
    public List<List<Node>> getAllPaths(Node startNode, Node endNode, boolean excludeStartAndEnd) {
        if(endNode == null){
            return getAllPaths(startNode, true);
        }
        List<List<Node>> paths = new ArrayList<>();
        List<Node> currentPath = new ArrayList<>();
        Set<Node> visited = new HashSet<>();
        dfsGetAllPaths(startNode, endNode, currentPath, paths, visited);
        if(excludeStartAndEnd){
            return paths.stream()
                    .map(list -> list.stream()
                            .filter(node -> !node.getId().equals(startNode.getId()) && !node.getId().equals(endNode.getId()))
                            .collect(Collectors.toList()))
                    .collect(Collectors.toList());
        }
        return paths;
    }

    private void dfsGetAllPaths(Node currentNode, List<Node> currentPath, List<List<Node>> paths, Set<Node> visited) {
        currentPath.add(currentNode);
        visited.add(currentNode);

        if (getNextNode(currentNode).isEmpty()) {
            // 当前节点是终点，将路径加入结果
            paths.add(new ArrayList<>(currentPath));
        } else {
            // 继续深度优先搜索
            for (Node nextNode : getNextNode(currentNode)) {
                if (!visited.contains(nextNode)) {
                    dfsGetAllPaths(nextNode, currentPath, paths, visited);
                }
            }
        }

        // 回溯
        currentPath.remove(currentPath.size() - 1);
        visited.remove(currentNode);
    }

    private void dfsGetAllPaths(Node currentNode, Node endNode, List<Node> currentPath, List<List<Node>> paths, Set<Node> visited) {
        currentPath.add(currentNode);
        visited.add(currentNode);

        if (currentNode.equals(endNode)) {
            // 当前节点是结束节点，将路径加入结果
            paths.add(new ArrayList<>(currentPath));
        } else {
            // 继续深度优先搜索
            for (Node nextNode : getNextNode(currentNode)) {
                if (!visited.contains(nextNode)) {
                    dfsGetAllPaths(nextNode, endNode, currentPath, paths, visited);
                }
            }
        }

        // 回溯
        currentPath.remove(currentPath.size() - 1);
        visited.remove(currentNode);
    }

    public boolean isGroupNodeAndThenELWrapper(Node startNode, ELWrapper elWrapper){
        boolean isGroupNode = isGroupNode(startNode);
        if(isGroupNode && FlowConvertELUtil.isThenELWrapper(elWrapper)){
            return true;
        }
        return false;
    }

    public boolean isGroupNodeAndWhenELWrapper(Node startNode, ELWrapper elWrapper){
        boolean isGroupNode = isGroupNode(startNode);
        if(isGroupNode && FlowConvertELUtil.isWhenELWrapper(elWrapper)){
            return true;
        }
        return false;
    }

    public boolean isGroupNode(Node startNode){
        if(CommonUtil.collUtil.isNotEmpty(groupParallelList)){
            for (Node node : groupParallelList){
                if(node.getChildren().contains(startNode.getId())){
                    return true;
                }
                List<Node> nodeList = list.get(startNode);
                Set<String> set = nodeList.stream().map(Node::getId).collect(Collectors.toSet());
                boolean flag = ELJsonUtil.retainAll(node.getChildren(), set);
                if(flag){
                    return true;
                }
            }
        }
        return false;
    }

    public Node getGroupNode(Node startNode, Integer groupType){
        List<Node> groupList = null;
        if(groupType == 0){
            groupList = this.groupParallelList;
        }else if(groupType == 3){
            groupList = this.groupCatchList;
        }
        if(CommonUtil.collUtil.isNotEmpty(groupList)){
            for (Node node : groupList){
                if(node.getChildren().contains(startNode.getId())){
                    return node;
                }
                List<Node> nodeList = list.get(startNode);
                Set<String> set = nodeList.stream().map(Node::getId).collect(Collectors.toSet());
                boolean flag = ELJsonUtil.retainAll(node.getChildren(), set);
                if(flag){
                    return node;
                }
            }
        }
        return null;
    }

    public Node getGroupNode(Node startNode){
        return getGroupNode(startNode, 0);
    }

    public ELWrapper catchGroupProp(Node startNode, ELWrapper wrapper){
        Node groupNode = getGroupNode(startNode, 3);
        if(groupNode != null){
            return ELBusCatch.catchException(wrapper);
        }
        return wrapper;
    }

    public void setGroupNodeProp(Node startNode,WhenELWrapper when){
        Node groupNode = getGroupNode(startNode);
        if(groupNode != null){
            NodeProperties properties = groupNode.getProperties();
            if(properties.getWhenIgnoreError() != null){
                when.ignoreError(properties.getWhenIgnoreError());
            }
            if(properties.getWhenAny() != null){
                if(properties.getWhenAny()){
                    when.any(properties.getWhenAny());
                }else{
                    String[] split = CommonUtil.split(properties.getWhenMust());
                    if(split != null){
                        when.must(split);
                    }
                }
            }
        }
    }

    public void setGroupNodeProp(List<Node> startNodeList,WhenELWrapper when){

    }

}
